Assuming you are familiar with C#;
If I give you a Type and tell you to create an object with it, you would automatically think of Activator.CreateInstance right?
What if I tell you that instanciating a Type using Expression Trees is much faster?
The code for the benchmarks is in this repository.
Background
I am currently in an internship and the project I’m working on needs to be as extensible as possible.
Meaning that users/developers should have the possiblity to add new functionalities (or at least a variant of a functionality) easily.
For instance, I have a Manager class and a couple of default classes that inherit implement, each of them does something specific.
Now the idea is to make it possible for people to plug-in their SomethingManager class.
I ended up with a list of Type that inherit from Manager. Now all I have to do is instanciate and execute these types when I need them. Like most C# programmers, I immediately thought of Activator.CreateInstance!
However, there must be a faster way for instanciating a type right?
Instaciating a type - some of the ways
Let’s imagine that our base class is TestClass.
Activator
[Benchmark]
public TestClass Activator()
{
return (TestClass)System.Activator.CreateInstance(_type);
}
ConstructorInfo (Reflection)
// This line should only be ran once
var _constructor = _type.GetConstructor(Type.EmptyTypes);
[Benchmark]
public TestClass Constructor()
{
return (TestClass)_constructor.Invoke(null);
}
Expression Trees
// This line should only be ran once
var _delegate = Expression.Lambda(Expression.New(_type)).Compile();
[Benchmark]
public TestClass Delegate()
{
return (TestClass)_delegate.DynamicInvoke();
}
Func<object> (Expression Trees)
// This line should only be ran once
var _func = Expression.Lambda<Func<object>>(Expression.New(_type)).Compile();
[Benchmark]
public TestClass Func()
{
return (TestClass)_func();
}
Func<TestClass> (Expression Trees)
// This line should only be ran once
var _typedFunc = Expression.Lambda<Func<TestClass>>(Expression.New(_type)).Compile();
[Benchmark]
public TestClass TypedFunc()
{
return _typedFunc();
}
Results
Benchmark results
I added the new TestClass() benchmark as a baseline.
As you can see, Expression Trees compiled Func<object> or Func<TestClass> are so close to being as fast as the baseline.
On the other hand, Expression Trees compiled to a plain Delegate and called using DynamicInvoke() are extremely slow.
This is due to the fact that a Delegate is dynamically invoked, .net has to use Reflection to figure out the Type and other informations, and this happens everytime.
For more informations, check this Stackoverflow question.
Raw results
| Method | Job | Mean | Error | StdDev | Ratio | RatioSD | Rank |
|---|---|---|---|---|---|---|---|
| New | Clr | 3.722 ns | 0.0581 ns | 0.0544 ns | 1.00 | 0.00 | 2 |
| Activator | Clr | 48.439 ns | 0.1852 ns | 0.1733 ns | 13.02 | 0.20 | 9 |
| Constructor | Clr | 137.333 ns | 0.3617 ns | 0.3383 ns | 36.91 | 0.54 | 11 |
| Delegate | Clr | 727.431 ns | 1.6431 ns | 1.2829 ns | 195.50 | 3.15 | 17 |
| Func | Clr | 11.439 ns | 0.0742 ns | 0.0658 ns | 3.07 | 0.04 | 7 |
| TypedFunc | Clr | 10.749 ns | 0.0752 ns | 0.0703 ns | 2.89 | 0.04 | 6 |
| New | Core | 3.973 ns | 0.0417 ns | 0.0370 ns | 1.07 | 0.02 | 3 |
| Activator | Core | 43.673 ns | 0.1425 ns | 0.1333 ns | 11.74 | 0.16 | 8 |
| Constructor | Core | 96.602 ns | 0.3215 ns | 0.3007 ns | 25.96 | 0.39 | 10 |
| Delegate | Core | 524.034 ns | 1.1478 ns | 1.0736 ns | 140.84 | 2.10 | 16 |
| Func | Core | 6.282 ns | 0.2092 ns | 0.3195 ns | 1.72 | 0.09 | 5 |
| TypedFunc | Core | 4.538 ns | 0.0891 ns | 0.0833 ns | 1.22 | 0.02 | 4 |
| New | CoreRT | 3.187 ns | 0.0759 ns | 0.0710 ns | 0.86 | 0.02 | 1 |
| Activator | CoreRT | 328.310 ns | 1.3142 ns | 1.2293 ns | 88.23 | 1.24 | 15 |
| Constructor | CoreRT | 142.282 ns | 0.7171 ns | 0.6708 ns | 38.24 | 0.55 | 12 |
| Delegate | CoreRT | 298.431 ns | 2.0301 ns | 1.8990 ns | 80.21 | 1.36 | 14 |
| Func | CoreRT | 220.747 ns | 0.5149 ns | 0.4816 ns | 59.33 | 0.84 | 13 |
| TypedFunc | CoreRT | 219.806 ns | 1.5278 ns | 1.4291 ns | 59.08 | 1.02 | 13 |
To sum up, instanciating a type at runtime can be done in multiple ways.
The fasted way (in these benchmarks) is using Expression Trees to generate a typed Func<X> and invoke it when needed.
For the future, I should also benchmark running IL Code directly.